from __future__ import print_function
from __future__ import absolute_import

import stackless
import sys
import math
import logging
import traceback
import math
import io
import os
from os import path

import wx
from wx import glcanvas
from wx import propgrid as wxpg

from kharlia import wxinter, core, engine, gamewindow
from kharlia.engine import FTileData, FSizeI, FRectI, FRectF, FVector3F, FVector2I
from kharlia.engine import TileFromCoords, TileToX, TileToY

from .generated import *

# Logger object for the editor
GLogger = None

# Current editor state, stored in an object in case tab support is added
# in the future.
GState = None

GPlayWorld = None
GPlayActor = None
GPlayCamera = None

def MoveCamera(delta):
    '''Move the camera by delta.'''
    GState.CameraTransform.Move(delta)

def GetTileBrush():
    '''Get the current tile brush as FTileData.'''
    return GState.Engine.EditorBrush

def SetTileBrush(width, height, tilecodes):
    '''Create and set the current tile brush from a list of tile codes.'''
    GState.Engine.EditorBrush = FTileData(FSizeI(width, height), tilecodes)
    GState.Engine.EditorBrushVisible = True

def ClearTileBrush():
    '''Clear the tile brush.'''
    GState.Engine.EditorBrush = None
    GState.Engine.EditorBrushVisible = False

def ApplyTileBrush(pos):
    '''Paste the tiles in the current brush at pos on the current layer.'''
    GState.Level.SetTiles(GState.TileLayer, pos, GState.Engine.EditorBrush)
    SetLevelDirty(True)

def GetSelection():
    '''Get the selection rect.'''
    return GState.Engine.EditorSelection

def SetSelection(selection):
    '''Set the selection rect.'''
    GState.Engine.EditorSelection = selection
    GState.Engine.EditorSelectionVisible = True
    
def ClearSelection():
    '''Clear the selection rect.'''
    GState.Engine.EditorSelection = FRectF()
    GState.Engine.EditorSelectionVisible = False

def ConvertScreenToTileCoords(pos):
    '''Convert screen coordinates in pixels to tile coordinates.'''
    return GState.Client.ConvertScreenToTileCoords(pos)

# TODO: Make these level handling functions clear about what parts of the
#       state they change.

def NewLevel(world, guid, name, size, defaulttile=16):
    '''Create a new level and set it as the current level.'''
    if IsLevelLoaded():
        ClearLevel(destroy=True)
    level = world.CreateLevel(guid, name, size)
    level.CreateLayer(0)
    level.SetTiles(0, defaulttile)
    SetLevel(level)
    SetLevelDirty(True)
    return level

def IsPlaying():
    return GPlayWorld is not None

def PlayLevel(pos):
    global GPlayWorld, GPlayActor, GPlayCamera
    if IsPlaying():
        EndPlay()
    assert not IsPlaying()
    assert IsLevelLoaded()

    world = None
    try:
        # Levels can currently be copied by pickling then unpickling
        
        # Pickle current editing level
        level = engine.CLevel.Pickle(GState.Level)
        
        # Create the play world    
        world = engine.CWorld()
        world.Init(GState.Engine)
    
        # Unpickle level into the world
        level = engine.CLevel.Unpickle(world, level)
        assert level.World is world
    
        # Start the game
        world.GameClass = engine.CGame
        world.StartGame()
    
        # Create the player controller
        pc = world.CreatePlayerActor(world.Engine.LocalPlayer)
        assert isinstance(pc, engine.APlayerController)
    
        # Create the player actor
        pawncomps = (
            engine.ATransform,
            engine.AMovement,
            engine.APhysics,
            engine.ACamera,
            engine.ARender,
            engine.APawn
            )
    
        pawnact = level.CreateActor(pawncomps)
        pawnact.Tag = u'EditorPlayer'
        pawnact.Transform.Position = pos
        
        # Possess the pawn
        pc.Possess(pawnact.Pawn)
        
        # Set up movement target
        GState.Engine.TestActor = pawnact
        GState.Engine.TestMovement = pawnact.Movement
        
        # Switch to the play world
        GState.Engine.World = world
        GState.Client.Camera = pawnact.Camera
    
        GPlayWorld = world
        GPlayActor = pc.Owner
        GPlayCamera = pawnact.Camera
    except Exception as e:
        GLogger.error('unable to create play world: %s', e)
        if world is not None:
            world.Exit()
    
        
def EndPlay():
    global GPlayWorld, GPlayActor, GPlayCamera
    assert IsPlaying()
    
    assert GPlayWorld is not GState.World
    
    GState.Engine.TestActor = None
    GState.Engine.TestMovement = None
    
    GState.Engine.World = GState.World
    GState.Client.Camera = GState.Camera
    
    GPlayCamera = None
    GPlayActor = None
    GPlayWorld.Exit()
    GPlayWorld = None

def ClearLevel(destroy=False):
    '''Clear the current level and optionally destroy it.
    
    Returns the cleared level object if not destroyed. Also removes the
    transient camera.
    '''
    assert GState.World
    assert GState.Level
    SetLevelDirty(False)
    SetLevelPath(None)
    if destroy:
        GState.World.DestroyLevel(GState.Level)
    else:
        lev = GState.Level
        GState.Level.DestroyActor(GState.Camera.Owner)
    GState.Level = None
    GState.Camera = None
    GState.CameraTransform = None
    if GState.Client:
        GState.Client.Camera = None
    if not destroy:
        return lev

def SetLevel(level):
    '''Set level as the currently viewed level. Creates the editor camera.'''
    assert GState.Client
    assert not GState.Level
    GState.Level = level
    GState.World = level.World
    cam = CreateEditorCamera(level)
    GState.Camera = cam.Camera
    GState.CameraTransform = cam.Transform
    GState.Client.Camera = GState.Camera

def LoadLevel(path, name=None):
    '''Load a level package and set it as the current level.
    
    Destroys the current level if one is set.
    '''
    assert GState.World

    GLogger.debug('loading level from %s', path)
    if GState.Level:
        GLogger.debug('destroying current level')
        ClearLevel(destroy=True)
    file = io.open(path, 'rb')
    lev = engine.CLevel.LoadPackage(GState.World, file, name)
    file.close() 
    SetLevel(lev)
    SetLevelDirty(False)
    if not isinstance(path, unicode):
        path = unicode(path)
    SetLevelPath(path)
    GLogger.debug('finished load')

def SaveLevel(path):
    '''Save the current level to a package. Overwrites destination.'''
    assert IsLevelLoaded()
    
    GLogger.debug('saving level to %s', path)
    file = io.open(path, 'wb')
    engine.CLevel.SavePackage(GState.Level, file)
    file.close()
    SetLevelDirty(False)
    GLogger.debug('finished save')
    
    # Test reloading
    #if 0:
    #    sleep = core.CTasker.GetDefault().Sleep
    #    sleep(2.0)
    #    
    #    GLogger.debug('destroying current level...')
    #    
    #    lev = GState.Level
    #    GState.Client.Camera = None
    #    GState.CameraTransform = None
    #    GState.Camera = None
    #    GState.Level = None
    #    GState.World.DestroyLevel(lev)
    #    lev = None
    #    
    #    GLogger.debug('destroyed level')
    #    sleep(3.0)
    #    GLogger.debug('loading level from autosave...')
    #    
    #    file = io.open(path, 'rb')
    #    lev = engine.CLevel.LoadPackage(GState.World, file, u'NewName')
    #    file.close()
    #    
    #    camact = CreateEditorCamera(lev)
    #    cam = camact.Camera
    #    trans = camact.Transform
    #    
    #    GState.Level = lev
    #    GState.Camera = cam
    #    GState.CameraTransform = trans
    #    GState.Client.Camera = cam
    #    GLogger.debug('loaded level')
    #    #GState.World.DestroyLevel(lev)
    
    #GLogger.debug('starting test load')
    #name += u'TestAppend'
    #GLogger.debug('loading level')
    #file = io.open(path, 'rb')
    #engine.CLevel.
    #lev = engine.CLevel.Unpickle(GState.World, pstr, name)
    #GLogger.debug('finished loading level')
    #GState.World.DestroyLevel(lev)

def GetLevelPath():
    path = GState.LevelPath
    assert isinstance(path, unicode)
    return path

def SetLevelPath(name):
    GState.LevelPath = name

def IsLevelLoaded():
    '''Check if a level is currently loaded.'''
    if GState.Level:
        assert GState.World
        assert GState.Camera
        assert GState.CameraTransform
        return True
    else:
        return False

def IsLevelDirty():
    '''Check if changes have been made to the level since last save.'''
    return GState.LevelDirty

def SetLevelDirty(value):
    assert GState.Level
    GState.LevelDirty = value

def CreateEditorCamera(level):
    '''Create the transient editor camera actor. Returns the new actor.'''
    cam = level.CreateActor((engine.ATransform, engine.ACamera))
    cam.Tag = u'EditorCamera'
    cam.Transient = True
    return cam

def GetPlaceableComponentNames():
    '''Return list of class names for placeable components.'''
    names = []
    for i in engine.GComponentClasses.itervalues():
        if getattr(i, 'PlaceableClass', False):
            names.append(i.__name__)
    names.sort()
    return names

def GenerateTileCodes(x, y, width, height):
    '''Generate a list of tile codes for the specified rectangle.'''
    tiles = []
    tiles_append = tiles.append
    convert = TileFromCoords
    for i in xrange(height):
        for j in xrange(width):
            tiles_append(convert(x + j, y + i))
    return tiles

class CEditorState(object):
    '''Class used to hold the current state of the editor.'''
    def __init__(self):
        self.Reset()
        
    def Reset(self):
        self.Engine = None
        self.Client = None
        self.World = None
        self.Level = None
        self.LevelPath = None
        self.LevelDirty = False
        self.Camera = None
        self.CameraTransform = None
        self.TileLayer = 0
        self.SelectedTiles = None

class CEditorViewport(gamewindow.CViewport):
    def __init__(self, *args, **kwds):
        gamewindow.CViewport.__init__(self, *args, **kwds)
        self.LastX = 0
        self.LastY = 0
        self.Dragging = False
        
    def OnClientInit(self, client):
        super(CEditorViewport, self).OnClientInit(client)
        assert GState.Client is client
        assert GState.Engine is client.Engine
    
    def OnLeftMouseDown(self, evt):
        super(CEditorViewport, self).OnLeftMouseDown(evt)
        
        if not IsLevelLoaded():
            return
        
        if IsPlaying():
            self.SetFocus()
            return
        
        brush = GetTileBrush()
        selection = GetSelection()
        if brush:
            # Find the corresponding tile coordinate for our mouse
            # and paste the tiles at that location.
            
            mousepos = FVector2I(evt.GetX(), evt.GetY())
            tilepos = ConvertScreenToTileCoords(mousepos)
            rect = FRectF(math.floor(tilepos.X),
                          math.floor(tilepos.Y),
                          brush.Size.Width,
                          brush.Size.Height)
            SetSelection(rect)
            #GLogger.debug('placing tiles at: {0}, {1}, {2}, {3}'.format(rect.X, rect.Y, rect.Width, rect.Height))
            #GState.Level.SetTiles(GState.TileLayer, rect.Pos, self.Engine.EditorBrush)
            ApplyTileBrush(rect.Pos)
            #ClearTileBrush(self.Engine)
            #self.Engine.EditorBrush = None
        elif selection:
            # Check if we clicked inside the selection
            mousex, mousey = ConvertScreenToTileCoords((evt.GetX(), evt.GetY()))
            if (mousex >= selection.Left and
                    mousex <= selection.Right and
                    mousey >= selection.Top and
                    mousey <= selection.Bottom):
                pass
            else:
                ClearSelection()
        else:
            ClearSelection()

    def OnRightMouseDown(self, evt):
        if IsLevelLoaded():
            self.Dragging = True
        if GetTileBrush() is not None:
            ClearTileBrush()
        super(CEditorViewport, self).OnRightMouseDown(evt)

    def OnRightMouseUp(self, evt):
        if self.Dragging:
            self.Dragging = False
        super(CEditorViewport, self).OnRightMouseUp(evt)

    def OnMouseMotion(self, evt):
        if self.Dragging:
            assert IsLevelLoaded()
            vec = FVector3F((float(self.LastX) - float(evt.GetX())) / 16,
                            (float(self.LastY) - float(evt.GetY())) / 16,
                            0.0)
            MoveCamera(vec)
        self.LastX = evt.GetX()
        self.LastY = evt.GetY()
        super(CEditorViewport, self).OnMouseMotion(evt)
        
    def OnKeyDown(self, event):
        super(CEditorViewport, self).OnKeyDown(event)
    
    def OnKeyUp(self, event):
        super(CEditorViewport, self).OnKeyUp(event)

class CTileSetPanel(wx.Panel):
    def __init__(self, engine, tileset, parent):
        wx.Window.__init__(self, parent)
        self.Engine = engine
        self.TilesetImage = wx.Bitmap(tileset)
        self.SetBackgroundColour(wx.Colour(40,40,40))
        self.currentThickness = 1
        self.Dragging = False
        self.selecting = True
        #self.SelectPos = [0,0]
        #self.SelectSize = [0,0]
        self.SelectPos = None
        self.SelectSize = None
        self.Selection = FRectI()
        self.Reselecting = False
        self.Buffer = None
        self.InitBuffer()
        self.Reset()
        self.SetSize(wx.Size(self.TilesetImage.GetWidth(),
                             self.TilesetImage.GetHeight()))
        for event, handler in [ \
            (wx.EVT_PAINT,self.OnPaint),
            (wx.EVT_LEFT_DOWN,self.OnLeftMouseDown),
            (wx.EVT_LEFT_UP,self.OnLeftMouseUp),
            (wx.EVT_SIZE, self.OnSize),
            (wx.EVT_MOTION, self.OnMouseMotion),
            (wx.EVT_ERASE_BACKGROUND,self.OnEraseBackground)
            ]:
            self.Bind(event,handler)
        
    def InitBuffer(self):
        self.Buffer = wx.EmptyBitmap(self.TilesetImage.GetWidth(),
                                     self.TilesetImage.GetHeight())
        dc = wx.BufferedDC(None, self.Buffer)
        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        
    def UpdateSelf(self):
        pass

    def Reset(self):
        dc = wx.BufferedDC(None, self.Buffer)
        mdc = wx.MemoryDC(self.TilesetImage)
        dc.Blit(0, 0, self.TilesetImage.GetWidth(),
                      self.TilesetImage.GetHeight(), mdc, 0, 0)
        self.Refresh() # re paint
    
    def OnEraseBackground(self, event):
        pass
    
    def OnPaint(self, event):
        wx.BufferedPaintDC(self, self.Buffer)

    def OnSize(self, event):
        self.SetSize(wx.Size(self.TilesetImage.GetWidth(),
                             self.TilesetImage.GetHeight()))
        self.UpdateSelf()
    
    def DrawTileset(self):
        dc = wx.BufferedDC(None, self.Buffer)
        mdc = wx.MemoryDC(self.TilesetImage)
        dc.Blit(0, 0, self.TilesetImage.GetWidth(),
                      self.TilesetImage.GetHeight(), mdc, 0, 0)

    def DrawSelection(self):
        selection = self.Selection
        dc = wx.BufferedDC(None, self.Buffer)
        dc.SetBrush(wx.Brush(wx.Colour(255, 0, 0), wx.TRANSPARENT))
        dc.SetLogicalFunction(wx.INVERT)
        if selection.Width != 0 and selection.Height != 0 and \
            dc is not None:
            dc.DrawRectangle(selection.X, selection.Y,
                             selection.Width, selection.Height)
            dc.DrawRectangle(selection.X - 1, selection.Y - 1,
                             selection.Width + 2, selection.Height + 2)
        
    def OnMouseMotion(self, event):
        if self.Dragging:
            select = event.GetPositionTuple()
            
            # undo last select draw
            if self.Selection.Area > 0:
                self.DrawSelection()
            
                
            #self.SelectSize = [(math.floor((select[0]+16)/16)*16)-self.Selection.X \
            #                  ,(math.floor((select[1]+16)/16)*16)-self.Selection.Y]
            #sw = math.ceil(float(select[0]) / 16) * 16
            sw = (math.floor((select[0] + 16) / 16) * 16) - self.Selection.X
            sh = (math.floor((select[1] + 16) / 16) * 16) - self.Selection.Y
            self.Selection.Size = FSizeI(int(sw), int(sh))

            #self.DrawTileset()
            self.DrawSelection()
            self.Refresh()
    
    def OnLeftMouseDown(self, event):
        select = event.GetPositionTuple()
        
        # undo last draw
        if self.Selection.Area > 0:
            mx, my = event.GetX(), event.GetY()
            print('MX:', mx, 'MY:', my)
            print('SP:', self.Selection.Pos)
            print('SS:', self.Selection.Size)
            if mx >= self.Selection.X and \
               mx <  self.Selection.X + self.Selection.Width and \
               my >= self.Selection.Y and \
               my <  self.Selection.Y + self.Selection.Height:
               print('DOING RESELECT')
               self.Reselecting = True
               reduced = FRectI(self.Selection)
               reduced /= FRectI(16, 16, 16, 16)
               tiles = GenerateTileCodes(reduced.X,
                                         reduced.Y,
                                         reduced.Width,
                                         reduced.Height)
               SetTileBrush(reduced.Width, reduced.Height, tiles)
               return

        self.Selection.Pos = (int(select[0] / 16) * 16,
                              int(select[1] / 16) * 16)
        self.Selection.Size = FSizeI(0, 0)
        print('SP:', self.Selection.Pos)
        self.DrawTileset()
        self.Dragging = True
        self.CaptureMouse()
        self.OnMouseMotion(event)
    
    def OnLeftMouseUp(self, event):
        if self.HasCapture():
            self.ReleaseMouse()
        
        if self.Reselecting:
            self.Dragging = False
            self.Reselecting = False
            return
        
        selection = self.Selection

        # straighted out a reversed rectangle
        if selection.Width < 0:
            selection.Width = abs(selection.Width)
            selection.X -= selection.Width
        if selection.Height < 0:
            selection.Height = abs(selection.Height)
            selection.Y -= selection.Height

        # clamp to tileset panel
        if selection.X < 0:
            selection.Width += selection.X
            selection.X = 0
        if selection.Y < 0:
            selection.Height += selection.Y
            selection.Y = 0
        tsw, tsh = self.TilesetImage.GetWidth(), self.TilesetImage.GetHeight()
        if selection.Right > tsw:
            selection.Width -= selection.Right - tsw
        if selection.Bottom > tsh:
            selection.Height -= selection.Bottom - tsh

        self.Dragging = False

        ## Convert the coordinates for the selected tile area to tile codes
        if selection.Area > 0:
            tiles = []
            reducedX = int(selection.X / 16)
            reducedY = int(selection.Y / 16)
            reducedWidth = int(selection.Width / 16)
            reducedHeight = int(selection.Height / 16)
            for i in xrange(reducedHeight):
                for j in xrange(reducedWidth):
                    tiles.append(TileFromCoords(reducedX + j, reducedY + i))
            SetTileBrush(reducedWidth, reducedHeight, tiles)
        else:
            ClearTileBrush()

class CNewLevelDialog(CNewLevelDialogBase):
    def __init__(self, parent, callback):
        CNewLevelDialogBase.__init__(self, parent)
        
        tgwb = self.FindWindowById(ID_TILEGROUP_WIDTH)
        tghb = self.FindWindowById(ID_TILEGROUP_HEIGHT)
        tgwb.Value = u'64'
        tghb.Value = u'64'
        
        self.Title = u'New Level'
        self.Callback = callback
        GLogger.info('created new map dialog')
        self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK)
        
    def OnOK(self, event):
        mname = self.FindWindowById(ID_NAME).GetValue()
        assert isinstance(mname, unicode)
        mw = int(self.FindWindowById(ID_WIDTH).GetValue())
        mh = int(self.FindWindowById(ID_HEIGHT).GetValue())
        #lw = int(self.FindWindowById(ID_TILEGROUP_WIDTH).GetValue())
        #lh = int(self.FindWindowById(ID_TILEGROUP_HEIGHT).GetValue())
        #print('new level params:', mname, mw, mh, lw, lh)
        self.Close()
        self.Callback(mname, mw, mh)
        #self.Callback(mname, mw, mh, lw, lh)
    
class CEditorMainFrame(wx.Frame):
    # parent, id, title, pos, size, style
    def __init__(self, engine):
        wx.Frame.__init__(self, parent=None, title='')
        self.EdGridVisible = False
        self.Engine = engine

        ## Create the main dialog, menu and status bars
        CMainPanel(self)
        self.SetMenuBar(CMainMenuBar())
        self.CreateStatusBar()
        self.SetStatusText('Ready...')

        ## Configure the splitter
        splitter = self.FindWindowById(ID_MAIN_SPLITTER)
        splitter.SetSashGravity(1.0)

        ## Replace the viewport placeholder
        vp_holder = self.FindWindowById(ID_VIEWPORT)
        vp_sizer = vp_holder.GetContainingSizer()
        vp_parent = vp_holder.GetParent()
        vp_holder.Destroy()
        self.Viewport = CEditorViewport(parent=vp_parent, id=ID_VIEWPORT, style=wx.WANTS_CHARS)
        vp_sizer.Add(self.Viewport, proportion=1, flag=wx.EXPAND)
        
        # Set the viewport's tab order
        notebook = self.FindWindowById(ID_MAIN_NOTEBOOK)

        ## Replace the tileset placeholer with an actual tileset
        tileset_holder = self.FindWindowById(ID_TILESET)
        tileset_sizer = tileset_holder.GetContainingSizer()
        tileset_parent = tileset_holder.GetParent()
        tileset_holder.Destroy()

        ## Make a scrolledwindow widget with scrollbars always on
        scrolling_window = wx.ScrolledWindow(tileset_parent,
                                             style=wx.HSCROLL|wx.VSCROLL|wx.ALWAYS_SHOW_SB) 
        scrolling_window.SetScrollRate(1,1)
        scrolling_window.EnableScrolling(True,True)

        ## Add it to tileset's flexgrid sizer
        tileset_sizer.Add(scrolling_window, proportion=1, flag=wx.EXPAND)

        ## Make a tileset widget and put it in the scrolling window
        tilesfilename = path.join(engine.Config['TilesetPath'],
                                  engine.Config['TestTilesetFileName'])
        self.tileset = CTileSetPanel(engine, tilesfilename, scrolling_window)

        ## Make a sizer for the tileset inside of the scrolled window
        tile_sizer = wx.BoxSizer(wx.VERTICAL)
        tile_sizer.Add(self.tileset)

        ## Assign the client sizer
        scrolling_window.SetSizer(tile_sizer)
        scrolling_window.SetBackgroundColour(wx.Colour(50,50,50))

        ## Populate the placeable actors list
        palist = self.FindWindowById(ID_PLACEABLE_COMPONENT_LIST)
        palist.InsertItems(GetPlaceableComponentNames(), 0)
        
        ## Create the layer property grid
        lop_dummy = self.FindWindowById(ID_LAYER_OPTIONS)
        assert lop_dummy, 'layer option placeholder not found'
        lop_sizer = lop_dummy.ContainingSizer
        lop_parent = lop_dummy.Parent
        lop_dummy.Destroy()
        del lop_dummy
        
        lop_pg = wxpg.PropertyGrid(
            parent=lop_parent,
            id=ID_LAYER_OPTIONS,
            style=wxpg.PG_AUTO_SORT |
                  wxpg.PG_SPLITTER_AUTO_CENTER |
                  wxpg.PG_DEFAULT_STYLE
        )
        lop_sizer.Add(lop_pg, proportion=1, flag=wx.EXPAND | wx.ALL)
        lop_pg.Append(wxpg.PropertyCategory(label=u'General'))
        lop_pg.Append(wxpg.IntProperty(name=u'Index', label=u'Index', value=0))
        lop_pg.Append(wxpg.StringProperty(name=u'Name', label=u'Name', value=u'Default Layer'))
        lop_pg.Append(wxpg.ColourProperty(name=u'Color', label=u'Color'))
        lop_pg.Append(wxpg.BoolProperty(name=u'NoCollision', label=u'No Collision', value=False))
        lop_pg.Append(wxpg.BoolProperty(name=u'Visible', label=u'Visible', value=True))

        ## Resize and bind now that the frame is setup
        self.SetSize(wx.Size(1200, 801))
        # File menu
        self.Bind(wx.EVT_MENU, self.OnNewLevel, id=wx.ID_NEW)
        self.Bind(wx.EVT_MENU, self.OnOpenLevel, id=wx.ID_OPEN)
        self.Bind(wx.EVT_MENU, self.OnSaveLevel, id=wx.ID_SAVE)
        self.Bind(wx.EVT_MENU, self.OnSaveLevelAs, id=wx.ID_SAVEAS)
        self.Bind(wx.EVT_MENU, self.OnCloseLevel, id=wx.ID_CLOSE)
        self.Bind(wx.EVT_MENU, self.OnQuit, id=wx.ID_EXIT)
        # View
        self.Bind(wx.EVT_MENU, self.OnToggleGrid, id=ID_SHOW_GRID)
        # Level
        self.Bind(wx.EVT_MENU, self.OnPlay, id=ID_PLAY)
        self.Bind(wx.EVT_MENU, self.OnEndPlay, id=ID_END_PLAY)
        # Help
        self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT)
        # Test
        self.Bind(wx.EVT_MENU, self.OnTest00, id=ID_TEST_00)
        # Other
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.UpdateTitle()
        
    def UpdateTitle(self):
        title = u'Kharlia Level Editor'
        if GState.LevelPath:
            title += ' ({0})'.format(GState.LevelPath)
        if GState.LevelDirty:
            title += ' *'
        self.Title = title
        
    def OnToggleGrid(self, event):
        self.EdGridVisible = not self.EdGridVisible
        # NOTE: engine.EdGridVisible should be a property
        self.Engine.EdGridVisible(self.EdGridVisible)

    def OnOpenLevel(self, event):
        dialog = wx.FileDialog(
            parent=self,
            message=u'Open level',
            wildcard=u'Level files (*.level)|*.level',
            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
            )
        result = dialog.ShowModal()
        if result == wx.ID_CANCEL:
            return
        filename = dialog.GetPath()
        if IsLevelLoaded():
            ClearLevel(destroy=True)
        LoadLevel(filename)

    def OnSaveLevel(self, event):
        path = GState.LevelPath
        if path is None:
            self.OnSaveLevelAs(event)
        else:
            SaveLevel(path)
        
    def OnSaveLevelAs(self, event):
        filename = GState.LevelPath
        if filename is None:
            filename = u''
        dialog = wx.FileDialog(
            parent=self,
            message=u'Save level',
            defaultFile=filename,
            wildcard=u'Level files (*.level)|*.level',
            style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
            )
        result = dialog.ShowModal()
        if result == wx.ID_CANCEL:
            return
        filename = dialog.GetPath()
        SaveLevel(filename)
        SetLevelPath(filename)

    def OnPlay(self, event):
        if not IsLevelLoaded():
            return
        PlayLevel(FVector3F(5.0, 5.0, 0.0))
        
    def OnEndPlay(self, event):
        if not IsLevelLoaded():
            return
        EndPlay()

    def OnCloseLevel(self, event):
        ClearLevel(destroy=True)

    def OnQuit(self,event):
        self.Close(True)

    def OnCloseWindow(self, event):
        self.Hide()
        self.Viewport.Destroy()
        self.Viewport = None
        self.Destroy()
        core.CApp.GetInstance().RequestExit()

    def OnAbout(self,event):
        pass # open up an about dialog
    
    def OnNewLevel(self, event):
        dialog = CNewLevelDialog(self, callback=self.OnNewLevelDialogOK)
        dialog.CentreOnParent()
        dialog.Show()

    def OnNewLevelDialogOK(self, lname, lw, lh):
        NewLevel(GState.World, None, lname, (lw, lh))

    def UpdateLayers(self):
        pass
        #layerlist = self.FindWindowById(ID_LAYER_LIST)
        #layerlist.InsertItems(['test'], layerlist.Count)
        
        #lnames = []
        #for index, layer in 
    def OnTest00(self, event):
        if not IsLevelLoaded():
            return
        level = GState.Level
        comps = (
                engine.ATransform,
                engine.AMovement,
                engine.APhysics,
                engine.ACamera,
                engine.ARender,
                engine.APawn
                )
        act = level.CreateActor(comps)
        act.Tag = u'TestActor00'
        act.Transform.Position = (5.0, 5.0, 0.0)
        trans = act.Transform
        trans._TestTasklet = core.CreateTasklet(TPTest)(act)

def TPTest(act):
    trans = act.Transform
    done = False
    current = stackless.getcurrent()
    
    while act.Level:
        try:
            if not act.Level:
                break
            trans.Move((4.0, 0.0, 0.0))
            ret = core.Sleep(3.0)
            print('SLEEPTIME:', ret)
            print('TEMPVAL:', current.tempval)
            if not act.Level:
                break
            trans.Move((0.0, 4.0, 0.0))
            ret = core.Sleep(3.0)
            print('SLEEPTIME:', ret)
            print('TEMPVAL:', current.tempval)
            if not act.Level:
                break
            trans.Move((-4.0, 0.0, 0.0))
            ret = core.Sleep(3.0)
            print('SLEEPTIME:', ret)
            print('TEMPVAL:', current.tempval)
            if not act.Level:
                break
            trans.Move((0.0, -4.0, 0.0))
            ret = core.Sleep(3.0)
            print('SLEEPTIME:', ret)
            print('TEMPVAL:', current.tempval)
            if not act.Level:
                break
        except Exception as e:
            if GLogger:
                GLogger.debug('exception in TPTest: %s', e)
                GLogger.debug('actor: %s', act)
                GLogger.debug('trans: %s', trans)
            raise

import copy_reg
copy_reg.constructor(TPTest)

# TODO: Decide whether to make subclass of Engine for editor or
#       seperate object        
        
def EditorMain():
    global GLogger, GState
    
    core.SetCurrentTaskletCritical(True)
    
    GLogger = logging.getLogger('editor')
    GLogger.debug('Initializing editor')
    GState = CEditorState()
    try:
        app = core.CApp.GetInstance()
        svcmgr = app.ServiceManager
        
        ensvc = svcmgr.GetServiceNoFail('kharlia.engine.CEngineService') 
        wxsvc = svcmgr.GetServiceNoFail('kharlia.wxinter.CWXService')
        
        assert ensvc.IsStarted and wxsvc.IsStarted
        
        eng = ensvc.Engine
            
        frame = wxsvc.CreateTopWindow(CEditorMainFrame, eng)
        eng.InitClient(frame.Viewport)
        eng.EditorEnabled = True
        
        GState.Engine = eng
        GState.Client = eng.Client

        world = engine.CWorld()
        world.Init(eng)
        eng.World = world
        lev = NewLevel(world, None, u'TestLevel', (3, 3))

        frame.UpdateLayers()

        app.TickRate = 0
        eng.ClientTickRate = 30

        sleep = core.Sleep
        while not app.ExitRequested:
            sleep(0.2)
            frame.UpdateTitle()

    finally:
        GState.Reset()
        GState = None
        GLogger.debug('Exiting editor')
        GLogger = None